admin / Strike
publicWeb-Based UK Cyber Compliance Tool with Reporting
Strike / StrikeXi v3 / backend / app / user_report.py
4047 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | """ Admin-only Users Report (PDF). Lists every account in the platform — both admin-created and self-service signups — with their profile details, role and account type. Rendered to PDF via WeasyPrint (consistent with the assessment report engine). """ import os from datetime import datetime from jinja2 import Template from weasyprint import HTML REPORTS_DIR = "/app/reports" def _origin_label(origin: str) -> str: return "Self-service signup" if origin == "self_service" else "Admin created" def _fmt(dt) -> str: return dt.strftime("%d %b %Y %H:%M") if dt else "—" _TEMPLATE = Template(r""" <!DOCTYPE html> <html><head><meta charset="utf-8"><style> @page { size: A4 landscape; margin: 1.4cm 1.2cm; @bottom-center { content: "StrikeXi Users Report — Confidential — Page " counter(page) " of " counter(pages); font-size: 9px; color: #8a97a6; } } body { font-family: "DejaVu Sans", sans-serif; color: #2c3e50; font-size: 11px; } .brand { font-size: 26px; font-weight: bold; color: #16324f; } .brand span { color: #2e86de; } .subtitle { font-size: 14px; color: #5b6b7b; margin: 2px 0 14px; } .meta { font-size: 11px; color: #5b6b7b; margin-bottom: 12px; } .meta b { color:#16324f; } h2 { color:#16324f; border-bottom: 2px solid #2e86de; padding-bottom: 4px; margin-top: 18px; font-size: 14px; } table { width:100%; border-collapse: collapse; margin-top:8px; } th, td { text-align:left; padding:5px 6px; border-bottom:1px solid #e3e9ef; font-size:9.5px; } th { background:#16324f; color:#fff; } .pill { font-size:9px; padding:1px 6px; border-radius:8px; } .ss { background:#eaf3fd; color:#2e86de; } .adm { background:#eef2f7; color:#5b6b7b; } .summary span { display:inline-block; margin-right:18px; font-size:12px; } </style></head><body> <div class="brand">Strike<span>Xi</span></div> <div class="subtitle">Platform Users Report</div> <div class="meta"> <b>Generated:</b> {{ generated }} · <b>By:</b> {{ admin }} · <b>Total accounts:</b> {{ users|length }} </div> <div class="summary"> <span><b>{{ count_admin_created }}</b> admin-created</span> <span><b>{{ count_self_service }}</b> self-service signups</span> <span><b>{{ count_admins }}</b> administrators</span> </div> <h2>All Users</h2> <table> <tr> <th>Username</th><th>Surname</th><th>First Name</th><th>Company</th> <th>Email</th><th>Contact</th><th>Role</th><th>Account Type</th> <th>Active</th><th>MFA</th><th>Last Login</th><th>Created</th> </tr> {% for u in users %} <tr> <td><b>{{ u.username }}</b></td> <td>{{ u.surname or '—' }}</td> <td>{{ u.first_name or '—' }}</td> <td>{{ u.company_name or '—' }}</td> <td>{{ u.email or '—' }}</td> <td>{{ u.contact_number or '—' }}</td> <td>{{ u.role }}</td> <td><span class="pill {{ 'ss' if u.signup_origin == 'self_service' else 'adm' }}">{{ origin_label(u.signup_origin) }}</span></td> <td>{{ 'Yes' if u.is_active else 'No' }}</td> <td>{{ 'On' if u.mfa_enabled else 'Off' }}</td> <td>{{ fmt(u.last_login) }}</td> <td>{{ fmt(u.created_at) }}</td> </tr> {% endfor %} </table> </body></html> """) def generate_users_report(users, admin_username: str) -> str: count_self_service = sum(1 for u in users if u.signup_origin == "self_service") count_admin_created = len(users) - count_self_service count_admins = sum(1 for u in users if u.role == "admin") html = _TEMPLATE.render( users=users, admin=admin_username, generated=datetime.now().strftime("%d %B %Y %H:%M"), count_self_service=count_self_service, count_admin_created=count_admin_created, count_admins=count_admins, origin_label=_origin_label, fmt=_fmt, ) os.makedirs(REPORTS_DIR, exist_ok=True) out_path = os.path.join(REPORTS_DIR, "StrikeXi_Users_Report.pdf") HTML(string=html).write_pdf(out_path) return out_path |